package net.aiion.weave.impl.cards.xml;

import net.aiion.weave.asm.Opcodes;
import net.aiion.weave.spi.bytecode.BytecodeGenerator;
import net.aiion.weave.spi.cards.CardContext;
import net.aiion.weave.spi.cards.CardXmlNames;
import net.aiion.weave.spi.cards.events.CardEvent;
import net.aiion.weave.spi.cards.expressions.CardExpressionResolverService;
import net.aiion.weave.spi.cards.expressions.CardExpressionTokenizerAction;
import net.aiion.weave.spi.cards.expressions.CardExpressionTokenizerService;
import net.aiion.weave.spi.injection.annotations.Inject;
import net.aiion.weave.spi.xml.XmlContext;
import net.aiion.weave.spi.xml.XmlGenerator;
import net.aiion.weave.spi.xml.XmlQualifiedName;
import net.aiion.weave.spi.xml.XmlSink;
import net.aiion.weave.spi.xml.events.AttributeXmlEvent;
import net.aiion.weave.spi.xml.events.CharactersXmlEvent;
import net.aiion.weave.spi.xml.events.ElementStartXmlEvent;
import net.aiion.weave.spi.xml.events.XmlEvent;

public class CardExpressionTransformation implements XmlSink
{
    private static final String PROP_INTERNAL_NAME = "internalName";

    private static final int INDEX_CARD_CONTEXT = 1;
    private static final int INDEX_ATTRIBUTE_STRING_BUILDER = 2;

    private static final String XML_GENERATOR_FIELD_NAME = "xmlGenerator$CardExpressionXmlSink";

    private BytecodeGenerator bytecodeGenerator;

    @Inject
    private CardExpressionTokenizerService cardExpressionTokenizerService;

    public CardExpressionTransformation() {
    }

    @Override
    public void handleXmlEvent(final XmlContext aXmlContext) {
        bytecodeGenerator = aXmlContext.getProperty("bytecodeGenerator", BytecodeGenerator.class);
        final XmlEvent xmlEvent = aXmlContext.getXmlEvent();
        switch (xmlEvent.getType()) {
        case CardEvent.CARD_CLASS_START:
            bytecodeGenerator.startField(Opcodes.ACC_PRIVATE, XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
            bytecodeGenerator.endField();
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        case CardEvent.CARD_PROCESS_CARD_METHOD_START:
            bytecodeGenerator.loadReference(0);
            bytecodeGenerator.loadReference(INDEX_CARD_CONTEXT);
            bytecodeGenerator.invokeInterface(CardContext.class, "getXmlGenerator", XmlGenerator.class);
            bytecodeGenerator.storeField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
                XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        case XmlEvent.CHARACTERS:
            handleCharactersXmlEvent(aXmlContext, xmlEvent.asCharacters());
            break;
        case XmlEvent.START_ELEMENT:
            handleElementStartXmlEvent(aXmlContext, xmlEvent.asElementStart());
            break;
        case XmlEvent.ATTRIBUTE:
            handleAttributeXmlEvent(aXmlContext, xmlEvent.asAttribute());
            break;
        default:
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        }
    }

    private void handleCharactersXmlEvent(final XmlContext aXmlContext, final CharactersXmlEvent aCharactersXmlEvent) {
        if (aCharactersXmlEvent.isWhitespace()) {
            aXmlContext.forwardXmlEvent(aCharactersXmlEvent);
            return;
        }

        cardExpressionTokenizerService.tokenizeExpressions(aCharactersXmlEvent.getData(),
            new CardExpressionTokenizerAction()
            {
                @Override
                public void handleTextToken(final String aTextToken) {
                    bytecodeGenerator.loadReference(0);
                    bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
                        XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
                    bytecodeGenerator.pushConstant(aTextToken);
                    bytecodeGenerator.pushInteger(aCharactersXmlEvent.getCharacterDataType());
                    bytecodeGenerator.invokeInterface(XmlGenerator.class, "writeCharacters", void.class, String.class,
                        int.class);
                }

                @Override
                public void handleExpressionToken(final String aExpressionPrefixToken,
                    final String aExpressionValueToken)
                {
                    bytecodeGenerator.loadReference(0);
                    bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
                        XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
                    bytecodeGenerator.loadReference(INDEX_CARD_CONTEXT);
                    bytecodeGenerator.invokeInterface(CardContext.class, "getExpressionResolverService",
                        CardExpressionResolverService.class);
                    bytecodeGenerator.pushConstant(aExpressionPrefixToken);
                    bytecodeGenerator.pushConstant(aExpressionValueToken);
                    bytecodeGenerator.invokeInterface(CardExpressionResolverService.class, "readExpression",
                        Object.class, String.class, String.class);
                    bytecodeGenerator.invokeStatic(String.class, "valueOf", Object.class, String.class);
                    bytecodeGenerator.pushInteger(aCharactersXmlEvent.getCharacterDataType());
                    bytecodeGenerator.invokeInterface(XmlGenerator.class, "writeCharacters", void.class, String.class,
                        int.class);
                }
            });
    }

    private void handleElementStartXmlEvent(final XmlContext aXmlContext,
        final ElementStartXmlEvent aElementStartXmlEvent)
    {
        if (CardXmlNames.NAMESPACE.equals(aElementStartXmlEvent.getName().getNamespaceURI())) {
            aXmlContext.forwardXmlEvent(aElementStartXmlEvent);
            return;
        }

        final XmlQualifiedName qualifiedName = aElementStartXmlEvent.getName();
        aXmlContext.getXmlGenerator().startElement(qualifiedName.getNamespaceURI(), qualifiedName.getLocalName(),
            qualifiedName.getPrefix());

        for (AttributeXmlEvent attributeXmlEvent : aElementStartXmlEvent.getAttributes()) {
            handleAttributeXmlEvent(aXmlContext, attributeXmlEvent);
        }
    }

    private void handleAttributeXmlEvent(final XmlContext aXmlContext, final AttributeXmlEvent aAttributeXmlEvent) {
        bytecodeGenerator.newReference(StringBuilder.class);
        bytecodeGenerator.duplicateReference();
        bytecodeGenerator.invokeConstructor(StringBuilder.class);
        bytecodeGenerator.storeReference(INDEX_ATTRIBUTE_STRING_BUILDER);

        cardExpressionTokenizerService.tokenizeExpressions(aAttributeXmlEvent.getValue(),
            new CardExpressionTokenizerAction()
            {
                @Override
                public void handleTextToken(final String aTextToken) {
                    bytecodeGenerator.loadReference(INDEX_ATTRIBUTE_STRING_BUILDER);
                    bytecodeGenerator.pushConstant(aTextToken);
                    bytecodeGenerator.invokeVirtual(StringBuilder.class, "append", StringBuilder.class, String.class);
                    bytecodeGenerator.popReference();
                }

                @Override
                public void handleExpressionToken(final String aExpressionPrefixToken,
                    final String aExpressionValueToken)
                {
                    bytecodeGenerator.loadReference(INDEX_ATTRIBUTE_STRING_BUILDER);
                    bytecodeGenerator.loadReference(INDEX_CARD_CONTEXT);
                    bytecodeGenerator.invokeInterface(CardContext.class, "getExpressionResolverService",
                        CardExpressionResolverService.class);
                    bytecodeGenerator.pushConstant(aExpressionPrefixToken);
                    bytecodeGenerator.pushConstant(aExpressionValueToken);
                    bytecodeGenerator.invokeInterface(CardExpressionResolverService.class, "readExpression",
                        Object.class, String.class, String.class);
                    bytecodeGenerator.invokeVirtual(StringBuilder.class, "append", StringBuilder.class, Object.class);
                    bytecodeGenerator.popReference();
                }
            });

        final XmlQualifiedName attributeQualifiedName = aAttributeXmlEvent.getName();

        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.pushConstant(attributeQualifiedName.getNamespaceURI());
        bytecodeGenerator.pushConstant(attributeQualifiedName.getLocalName());
        bytecodeGenerator.pushConstant(attributeQualifiedName.getPrefix());
        bytecodeGenerator.loadReference(INDEX_ATTRIBUTE_STRING_BUILDER);
        bytecodeGenerator.invokeVirtual(StringBuilder.class, "toString", String.class);
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "addAttribute", void.class, String.class, String.class,
            String.class, String.class);
    }
}
